﻿// <copyright Autor="twitter.com/gotactile">
// Distributed under Microsoft Public License (Ms-PL)
// </copyright>
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Windows.Controls;
using System.Windows;
using System.Windows.Input;
using System.Windows.Media.Animation;
using System.Windows.Media;
using System.Timers;
using System.Windows.Threading;
using System.Windows.Controls.Primitives;
using System.Diagnostics;

namespace Con10uum
{
    public class Con10uumPanel : Panel, IScrollInfo
    {
        static Con10uumPanel()
        {
            DefaultStyleKeyProperty.OverrideMetadata(typeof(Con10uumPanel), new FrameworkPropertyMetadata(typeof(Con10uumPanel)));
        }

        private EasingFunctionBase m_ResetZoomEasingFunction = new ExponentialEase();
        private int m_LastAddedCon10uumWindowLayoutPostion;
        private DispatcherTimer m_DispatcherTimer;
        private int m_AnimatingCon10uumWindowsCount;
        private const int m_ElasticMargin = 20;
        Stopwatch s = new Stopwatch();

        public Con10uumScrollData m_Con10uumScrollData = new Con10uumScrollData();
        
        public Con10uumPanel()
        {
           this.Initialized += new EventHandler(Con10uumPanel_Initialized);
        }
        
        public static readonly DependencyProperty OpenCloseAnimationDurationProperty = DependencyProperty.Register("OpenCloseAnimationDuration", typeof(Duration), typeof(Con10uumPanel), new PropertyMetadata(new Duration(TimeSpan.FromMilliseconds(300))));

        /// <summary>
        /// This property controls the duration of the animation that open/close Con10uumWindows.
        /// </summary>
        public Duration OpenCloseAnimationDuration
        {
            get { return (Duration)GetValue(OpenCloseAnimationDurationProperty); }
            set { SetValue(OpenCloseAnimationDurationProperty, value); }
        }

        public static readonly DependencyProperty WindowMinWidthProperty = DependencyProperty.Register("WindowMinWidth", typeof(double), typeof(Con10uumPanel), new PropertyMetadata(0.0));

        public double WindowMinWidth
        {
            get { return (double)GetValue(WindowMinWidthProperty); }
            set { SetValue(WindowMinWidthProperty, value); }
        }

        public static readonly DependencyProperty WindowMaxWidthProperty = DependencyProperty.Register("WindowMaxWidth", typeof(double), typeof(Con10uumPanel), new PropertyMetadata(double.PositiveInfinity));

        public double WindowMaxWidth
        {
            get { return (double)GetValue(WindowMaxWidthProperty); }
            set { SetValue(WindowMaxWidthProperty, value); }
        }

        public static readonly DependencyProperty ResistanceProperty = DependencyProperty.Register("Resistance", typeof(double), typeof(Con10uumPanel), new PropertyMetadata(0.0));

        public double Resistance
        {
            get { return (double)GetValue(ResistanceProperty); }
            set { SetValue(ResistanceProperty, value); }
        }

        public static readonly DependencyProperty MaxScrollVelocityProperty = DependencyProperty.Register("MaxScrollVelocity", typeof(double), typeof(Con10uumPanel), new PropertyMetadata(double.PositiveInfinity));
        
        public double MaxScrollVelocity
        {
            get { return (double)GetValue(MaxScrollVelocityProperty); }
            set { SetValue(MaxScrollVelocityProperty, value); }
        }

        public static readonly DependencyProperty SnapPrecisionProperty = DependencyProperty.Register("SnapPrecision", typeof(double), typeof(Con10uumPanel), new PropertyMetadata(0.1));

        /// <summary>
        /// 
        /// </summary>
        public double SnapPrecision
        {
            get { return (double)GetValue(SnapPrecisionProperty); }
            set
            {
                SetValue(SnapPrecisionProperty, value);
            }
        }

        #region Con10uumWindow private AttachedProperty

        private static readonly DependencyProperty Con10uumWindowAttachedDataProperty =
            DependencyProperty.RegisterAttached("Con10uumWindowAttachedData", typeof(Con10uumWindowAttachedData), typeof(Con10uumPanel), null);

        private static Con10uumWindowAttachedData GetCon10uumWindowAttachedData(DependencyObject obj)
        {
            // This uses a standard attached DP lazy-init pattern that will create the default value
            // for this property and set it if GetValue returned null. That means that this method
            // will never return null.

            object value = obj.GetValue(Con10uumWindowAttachedDataProperty);
            if (value == null)
            {
                Con10uumWindowAttachedData data = new Con10uumWindowAttachedData();
                SetCon10uumWindowAttachedData(obj, data);
                return data;
            }
            else
            {
                return (Con10uumWindowAttachedData)value;
            }
        }

        private static void SetCon10uumWindowAttachedData(DependencyObject obj, Con10uumWindowAttachedData value)
        {
            obj.SetValue(Con10uumWindowAttachedDataProperty, value);
        }

        #endregion

        public ScaleTransform ZoomScaleTransform
        {
            get
            {
                TransformGroup transformGroup = (this.RenderTransform as TransformGroup);
                return (transformGroup.Children as TransformCollection).OfType<ScaleTransform>().FirstOrDefault();
            }
        }

        public EasingFunctionBase ResetZoomEasingFunction
        {
            get { return m_ResetZoomEasingFunction; }
            set
            {
                if (value == null)
                {
                    m_ResetZoomEasingFunction = new ExponentialEase();
                }
                else
                {
                    m_ResetZoomEasingFunction = value;
                }
            }
        }

        void Con10uumPanel_Initialized(object sender, EventArgs e)
        {
            TransformGroup transformGroup = (this.RenderTransform as TransformGroup);
            if (transformGroup == null)
            {
                transformGroup = new TransformGroup();

                transformGroup.Children.Add(new ScaleTransform());
                transformGroup.Children.Add(new SkewTransform());
                transformGroup.Children.Add(new RotateTransform());

                m_Con10uumScrollData.ScrollTranslateTransform = new TranslateTransform();
                transformGroup.Children.Add(m_Con10uumScrollData.ScrollTranslateTransform);

                this.RenderTransform = transformGroup;
            }
            else if (transformGroup.IsFrozen == true)
            {
                //override the default frozen transformGroup
                transformGroup = transformGroup.Clone();
                m_Con10uumScrollData.ScrollTranslateTransform = (transformGroup.Children as TransformCollection).OfType<TranslateTransform>().FirstOrDefault();
                this.RenderTransform = transformGroup;
            }
            else
            {
                m_Con10uumScrollData.ScrollTranslateTransform = (transformGroup.Children as TransformCollection).OfType<TranslateTransform>().FirstOrDefault();
            }

            foreach (UIElement element in base.Children)
            {
                SetCon10uumWindowAttachedData(element, new Con10uumWindowAttachedData() { LayoutPosition = Children.IndexOf(element) });
            }
            
            m_LastAddedCon10uumWindowLayoutPostion = Children.Count - 1;
            
            m_DispatcherTimer = new DispatcherTimer(DispatcherPriority.Render);
            m_DispatcherTimer.Tick += new EventHandler(m_DispatcherTimer_Tick);
            m_DispatcherTimer.Interval = TimeSpan.FromMilliseconds(1);
            m_DispatcherTimer.Start();
            
            s.Start();
        }

        public static Storyboard GetStoryboardScaleTo03()
        {
            Storyboard storyboardScaleTo03 = new Storyboard();

            DoubleAnimationUsingKeyFrames d1 = new DoubleAnimationUsingKeyFrames();           
            d1.SetValue(Storyboard.TargetNameProperty, "Con10uumPanel");
            d1.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"));
            storyboardScaleTo03.Children.Add(d1);
            EasingDoubleKeyFrame e1 = new EasingDoubleKeyFrame(0.3, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(400)), new CubicEase() { EasingMode = EasingMode.EaseOut });
            d1.KeyFrames.Add(e1);

            DoubleAnimationUsingKeyFrames d2 = new DoubleAnimationUsingKeyFrames();
            d2.SetValue(Storyboard.TargetNameProperty, "Con10uumPanel");
            d2.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"));
            storyboardScaleTo03.Children.Add(d2);
            EasingDoubleKeyFrame e2 = new EasingDoubleKeyFrame(0.3, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(400)), new CubicEase() { EasingMode = EasingMode.EaseOut });
            d2.KeyFrames.Add(e2);

            return storyboardScaleTo03;
        }

        public static Storyboard GetStoryboardScaleTo1()
        {
            Storyboard storyboardScaleTo1 = new Storyboard();

            DoubleAnimationUsingKeyFrames d1 = new DoubleAnimationUsingKeyFrames();
            d1.SetValue(Storyboard.TargetNameProperty, "Con10uumPanel");
            d1.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleX)"));
            storyboardScaleTo1.Children.Add(d1);
            EasingDoubleKeyFrame e1 = new EasingDoubleKeyFrame(1.0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(400)), new CubicEase() { EasingMode = EasingMode.EaseOut });
            d1.KeyFrames.Add(e1);

            DoubleAnimationUsingKeyFrames d2 = new DoubleAnimationUsingKeyFrames();
            d2.SetValue(Storyboard.TargetNameProperty, "Con10uumPanel");
            d2.SetValue(Storyboard.TargetPropertyProperty, new PropertyPath("(UIElement.RenderTransform).(TransformGroup.Children)[0].(ScaleTransform.ScaleY)"));
            storyboardScaleTo1.Children.Add(d2);

            EasingDoubleKeyFrame e2 = new EasingDoubleKeyFrame(1.0, KeyTime.FromTimeSpan(TimeSpan.FromMilliseconds(400)), new CubicEase() { EasingMode = EasingMode.EaseOut });
            d2.KeyFrames.Add(e2);

            return storyboardScaleTo1;
        }

        /// <summary>
        /// This tells the layout system to call Arrange on the Panel if there are any elements that
        /// must be moved.
        /// </summary>
        void m_DispatcherTimer_Tick(object sender, EventArgs e)
        {
            // If there are still elements to be animated, make sure that ArrangeOverride will
            // get called. Note that the InvalidateArrange may already have been called due to
            // other operations, but calling it again does not hurt, and all of the work will
            // be done in ArrangeOverride regardless of where the invalidation came from.

            if (m_AnimatingCon10uumWindowsCount > 0 || m_Con10uumScrollData.GetAverageHorizontalOffsetVelocity() != 0 || m_Con10uumScrollData.HasNewElement || m_Con10uumScrollData.HasClosedElement)
            {
                InvalidateArrange();
            }
        }

        /// <summary>
        /// This method is required when subclassing from Panel. It figures out how big the childre are
        /// and where they should go. 
        /// </summary>
        protected override Size MeasureOverride(Size availableSize)
        {
            Size extent = new Size(0,0);//take all available vertical space by default
            bool viewportChanged = false;
            bool extentChanged = false;

            // Measure each child first. This is inefficient, but it is necessary in order
            // to have smooth animations of new children. Otherwise, the animations may jump 
            // due to the time that template expansion can take.
            Size youCanBeAsLargeAsYouWant = new Size(WindowMaxWidth, availableSize.Height);
            foreach (UIElement child in base.InternalChildren)
            {
                child.Measure(youCanBeAsLargeAsYouWant);

                //double ajustedChildWidth = Math.Max(child.DesiredSize.Width, WindowMinWidth);
                extent.Height += child.DesiredSize.Height;
                extent.Width += child.DesiredSize.Width;
            }

            #region Setup Animates Children(Con10uumWindow) Attach Data

            double nextVisualPosition = 0;
            DateTime now = DateTime.Now;

            m_AnimatingCon10uumWindowsCount = 0;

            // Now each the position from each child is computed. If the child is not where it is supposed
            // to be, set up a virtual animation to move it there.

            foreach (UIElement con10window in Con10windowsSortedByThereLayoutPosition())
            {
                Con10uumWindowAttachedData data = GetCon10uumWindowAttachedData(con10window);
                //double ajustedChildWidth = Math.Max(con10window.DesiredSize.Width, WindowMinWidth);
                double ajustedChildWidth = con10window.DesiredSize.Width;

                // If this is a new element, then start it off of the screen
                if (data.CurrentLeftPosition == Con10uumWindowAttachedData.Unset)
                {
                    data.CurrentLeftPosition = m_Con10uumScrollData.ScrollRegionExtentSize.Width + ajustedChildWidth;
                    m_Con10uumScrollData.HasNewElement = true;
                    m_Con10uumScrollData.ScrollToRightEndStartHorizontalOffset = HorizontalOffset;
                    m_Con10uumScrollData.ScrollToRightEndStartDate = now;
                }

                if (data.TargetLeftPosition != nextVisualPosition)
                {
                    // The target of this element is either brand new or has moved, so we need to 
                    // recalculate everything, and set up a virtual animation.

                    data.StartTime = now;
                    data.StartLeftPosition = data.CurrentLeftPosition;
                    data.TargetLeftPosition = nextVisualPosition;

                    // IsAnimating only gets set to true right here. This begins the virtual animation
                    // for this element.
                    data.IsAnimating = true;

                    ++m_AnimatingCon10uumWindowsCount;
                }
                else if (data.IsAnimating)
                {
                    // This item is still animating, so keep track of it, but since
                    // the target position has not changed, don't do anything else.

                    ++m_AnimatingCon10uumWindowsCount;
                }
                
                // Advance the position of the next element.
                nextVisualPosition += ajustedChildWidth;
            }

            #endregion

            if (this.Children.Count < m_Con10uumScrollData.Con10uumWindowCount)
            {
                m_Con10uumScrollData.HasClosedElement = true;
                m_Con10uumScrollData.ScrollLeftStartHorizontalOffset = HorizontalOffset;
                m_Con10uumScrollData.ScrollLeftStartDate = now;
            }
            m_Con10uumScrollData.Con10uumWindowCount = this.Children.Count;

            //Is this above part really needed ? In what scenario should be usefull ? 

            if (extent != m_Con10uumScrollData.ScrollRegionExtentSize)
            {
                m_Con10uumScrollData.ScrollRegionExtentSize = extent;
                extentChanged = true;
            }

            if (availableSize != m_Con10uumScrollData.ScrollRegionViewportSize)
            {
                m_Con10uumScrollData.ScrollRegionViewportSize = availableSize;
                viewportChanged = true;
            }

            //if ((extentChanged || viewportChanged) && ScrollOwner != null)
            //{
            //    SetHorizontalOffsetNoAnimation(HorizontalOffset);
            //}
            //availableSize.Width = Math.Max(availableSize.Width, WindowMinWidth);
            //availableSize.Width = Math.Min(availableSize.Width, WindowMaxWidth);

            if (availableSize.Width == Double.PositiveInfinity)
            {
                availableSize.Width = extent.Width;
            }
            if (availableSize.Height == Double.PositiveInfinity)
            {
                availableSize.Height = extent.Height;
            }
            return availableSize;
        }

        /// <summary>
        /// A "normal" ArrangeOverride would just put things where they belong. What this one does
        /// is to move the children towards their destinations according to the virtual animation
        /// data that has been attached to each element. When they get there, the virtual animation
        /// is turned off.
        /// </summary>
        protected override Size ArrangeOverride(Size finalSize)
        {
            Size extent = new Size(0, finalSize.Height);//take all available vertical space by default
            bool viewportChanged = false;
            bool extentChanged = false; 

            DateTime now = DateTime.Now;

            #region Arrange Children

            foreach (UIElement child in InternalChildren)
            {
                double ajustedChildWidth = Math.Max(child.DesiredSize.Width, WindowMinWidth);
                //double ajustedChildWidth = child.DesiredSize.Width;
                extent.Width += ajustedChildWidth;
                
                Con10uumWindowAttachedData data = GetCon10uumWindowAttachedData(child);

                TimeSpan elapsed = data.GetElapsed(now);

                if (elapsed < OpenCloseAnimationDuration || data.TargetLeftPosition != data.CurrentLeftPosition)
                {
                    // The virtual animation is not done yet, so figure out how far along it is...
                    double progress = (OpenCloseAnimationDuration.TimeSpan != TimeSpan.Zero) ? Math.Min(elapsed.TotalMilliseconds / OpenCloseAnimationDuration.TimeSpan.TotalMilliseconds, 1.0) : 1;

                    // ...and what the next position is.
                    Double newPosition = EasingDouble(m_ResetZoomEasingFunction, data.StartLeftPosition, data.TargetLeftPosition, progress);
                    child.Arrange(new Rect(newPosition, 0, ajustedChildWidth, this.DesiredSize.Height));
                    data.CurrentLeftPosition = newPosition;
                }
                else
                {
                    // This element is not animating, but it might have become invalid on its own, so it still
                    // needs to be arranged. The layout system will do as little as possible.
                    child.Arrange(new Rect(data.CurrentLeftPosition, 0, ajustedChildWidth, this.DesiredSize.Height));
                    if (data.IsAnimating)
                    {
                        --m_AnimatingCon10uumWindowsCount;

                        // This is the only place where IsAnimating is set to false. This turns off the virtual animation.
                        data.IsAnimating = false;

                        //reset the ScrollLeftStartDate to start the Scroll Animation now (after all Con10uumWindow animations finished)
                        if (m_Con10uumScrollData.HasClosedElement)
                        {
                            m_Con10uumScrollData.ScrollLeftStartDate = now;
                        }
                    }
                }
            }
            
            #endregion

            #region Animate Scrolling
           
            if (extent != m_Con10uumScrollData.ScrollRegionExtentSize)
            {
                m_Con10uumScrollData.ScrollRegionExtentSize = extent;
                extentChanged = true;
            }

            if (finalSize != m_Con10uumScrollData.ScrollRegionViewportSize)
            {
                m_Con10uumScrollData.ScrollRegionViewportSize = finalSize;

                //Reset ZoomScaleTransform Center
                Point center = new Point(finalSize.Width / 2, finalSize.Height / 10);
                this.ZoomScaleTransform.CenterX = center.X;
                this.ZoomScaleTransform.CenterY = center.Y;

                viewportChanged = true;
            }

            if (ScrollOwner != null)
            {
                double maxHorizontalOffsetExact = CalculateMaxHorizontalOffsetExact(HorizontalOffset);
                double offsetInsideMargin = HorizontalOffset - maxHorizontalOffsetExact;
                    
                if (m_Con10uumScrollData.HasNewElement) //scroll at right
                {                    
                    QuinticEase x = new QuinticEase();
                    x.EasingMode = EasingMode.EaseInOut;
                    double progress = Math.Min(((now - m_Con10uumScrollData.ScrollToRightEndStartDate).TotalMilliseconds / OpenCloseAnimationDuration.TimeSpan.TotalMilliseconds), 1.0);
                    double easeHorizontalOffset = EasingDouble(x, m_Con10uumScrollData.ScrollToRightEndStartHorizontalOffset, ExtentWidth - ViewportWidth, progress);
                    
                    //Set HorizontalOffset for newHorizontalOffset
                    double adjustedHorizontalOffset = CalculateMaxHorizontalOffsetExact(easeHorizontalOffset);
                    
                    easeHorizontalOffset = adjustedHorizontalOffset;

                    m_Con10uumScrollData.ScrollInfoOffset.X = easeHorizontalOffset;

                    if (ScrollOwner != null)
                    {
                        ScrollOwner.InvalidateScrollInfo();
                    }

                    ApplyScrollTranslateTransform(HorizontalOffset);                    

                    //Update Con10uumScrollVelocityData
                    m_Con10uumScrollData.ResetAverageHorizontalOffsetVelocity();
                    m_Con10uumScrollData.LastRecordedHorizontalOffset = HorizontalOffset;
                    m_Con10uumScrollData.LastRecordedHorizontalOffsetDate = now;

                    if (progress == 1)
                    {
                        m_Con10uumScrollData.HasNewElement = false;
                    }
                }
                else if (m_Con10uumScrollData.HasClosedElement) //scroll left only if needed
                {
                    double maxOffset = CalculateMaxHorizontalOffsetExact(Double.PositiveInfinity);
                    if (HorizontalOffset < maxOffset)
                    {
                        SetHorizontalOffsetNoAnimation(HorizontalOffset);
                        m_Con10uumScrollData.HasClosedElement = false;
                    }
                    else
                    {

                        QuadraticEase x = new QuadraticEase();
                        x.EasingMode = EasingMode.EaseOut;
                        double progress = Math.Min(((now - m_Con10uumScrollData.ScrollLeftStartDate).TotalMilliseconds / OpenCloseAnimationDuration.TimeSpan.TotalMilliseconds), 1.0);
                        double easeHorizontalOffset = EasingDouble(x, m_Con10uumScrollData.ScrollLeftStartHorizontalOffset, maxOffset, progress);

                        m_Con10uumScrollData.ScrollInfoOffset.X = easeHorizontalOffset;

                        if (ScrollOwner != null)
                        {
                            ScrollOwner.InvalidateScrollInfo();
                        }

                        ApplyScrollTranslateTransform(HorizontalOffset);

                        //Update Con10uumScrollVelocityData
                        m_Con10uumScrollData.ResetAverageHorizontalOffsetVelocity();
                        m_Con10uumScrollData.LastRecordedHorizontalOffset = HorizontalOffset;
                        m_Con10uumScrollData.LastRecordedHorizontalOffsetDate = now;

                        if (progress == 1)
                        {
                            m_Con10uumScrollData.HasClosedElement = false;
                        }
                    }                 
                }
                else if (m_Con10uumScrollData.GetAverageHorizontalOffsetVelocity() != 0)// auto scroll with velocity
                {
                    var touchcount = this.ScrollOwner.TouchesOver.Count();
                    if (touchcount > 0 || Mouse.LeftButton == MouseButtonState.Pressed)
                    {
                        //double realMargin = (3.0 / 2.0) * offsetInsideMargin;
                        //double realHorizontalOffset = HorizontalOffset - offsetInsideMargin + realMargin;

                        //if (Math.Abs(offsetInsideMargin) > 0.0)
                        //{
                        //}
                        
                        //user is scrolling : just update the m_Con10uumScrollData.ScrollTranslateTransform position
                        SetHorizontalOffsetNoAnimation(HorizontalOffset);                        
                    }
                    else if (Math.Abs(offsetInsideMargin) > 0.0)
                    {
                        if (Math.Abs(offsetInsideMargin) == m_ElasticMargin)
                        {
                            m_Con10uumScrollData.ScrollToBoundaryStartDate = now;
                        }

                        //go back to viewport boundary
                        TimeSpan elapsed = now - m_Con10uumScrollData.ScrollToBoundaryStartDate;
                        double progress = Math.Min(elapsed.TotalMilliseconds / OpenCloseAnimationDuration.TimeSpan.TotalMilliseconds, 1.0);
                        
                        QuinticEase x = new QuinticEase();
                        x.EasingMode = EasingMode.EaseOut;
                        double easeScale;
                        if(offsetInsideMargin < 0)
                        {
                            easeScale = EasingDouble(x, -m_ElasticMargin-1, 0, progress);
                        }
                        else
                        {
                            double d = CalculateMaxHorizontalOffsetExact(Double.PositiveInfinity);
                            easeScale = EasingDouble(x, d + m_ElasticMargin-1, d, progress);
                        }
                  
                        m_Con10uumScrollData.ScrollInfoOffset.X = easeScale;

                        if (ScrollOwner != null)
                        {
                            ScrollOwner.InvalidateScrollInfo();
                        }

                        ApplyScrollTranslateTransform(HorizontalOffset);

                        //Update Con10uumScrollVelocityData
                        
                        m_Con10uumScrollData.LastRecordedHorizontalOffset = HorizontalOffset;
                        m_Con10uumScrollData.LastRecordedHorizontalOffsetDate = now;
                    }
                    else
                    {
                        //user stop to scroll. Apply iniertia
                        double newHorizontalOffset;
                        double newVelocity;
                        double positiveOrNegative = (m_Con10uumScrollData.GetAverageHorizontalOffsetVelocity() < 0.0) ? -1.0 : 1.0;
                        ApplyInertia(HorizontalOffset, Math.Min(Math.Abs(m_Con10uumScrollData.GetAverageHorizontalOffsetVelocity()), MaxScrollVelocity) * positiveOrNegative, out newHorizontalOffset, out newVelocity);

                        //Set HorizontalOffset for newHorizontalOffset
                        double adjustedHorizontalOffset = CalculateMaxHorizontalOffsetExact(newHorizontalOffset);
                        if (newHorizontalOffset != adjustedHorizontalOffset)
                        {
                            newVelocity = 0;
                        }
                        newHorizontalOffset = adjustedHorizontalOffset;

                        m_Con10uumScrollData.ScrollInfoOffset.X = newHorizontalOffset;

                        if (ScrollOwner != null)
                        {
                            ScrollOwner.InvalidateScrollInfo();
                        }

                        ApplyScrollTranslateTransform(HorizontalOffset);

                        //Update Con10uumScrollVelocityData
                        m_Con10uumScrollData.ResetAverageHorizontalOffsetVelocity(newVelocity);
                        m_Con10uumScrollData.LastRecordedHorizontalOffset = HorizontalOffset;
                        m_Con10uumScrollData.LastRecordedHorizontalOffsetDate = now;
                    }
                }
                else if ((extentChanged || viewportChanged)) // Layout updated (WindowSize changed for exemple)
                {
                    //re set HorizontalOffset without animation
                    SetHorizontalOffsetNoAnimation(HorizontalOffset);
                }
            }
            
            #endregion
            
            return finalSize;
        }

        private IEnumerable<UIElement> Con10windowsSortedByThereLayoutPosition()
        {
            List<UIElement> list = new List<UIElement>();
            foreach (UIElement child in InternalChildren)
            {
                list.Add(child);
            }
            list.Sort(delegate(UIElement fe1, UIElement fe2)
                {
                    if (GetCon10uumWindowAttachedData(fe1).LayoutPosition == -1)
                    {
                        GetCon10uumWindowAttachedData(fe1).LayoutPosition = m_LastAddedCon10uumWindowLayoutPostion+1;
                        m_LastAddedCon10uumWindowLayoutPostion++;
                    }
                    if (GetCon10uumWindowAttachedData(fe2).LayoutPosition == -1)
                    {
                        GetCon10uumWindowAttachedData(fe2).LayoutPosition = m_LastAddedCon10uumWindowLayoutPostion+1;
                        m_LastAddedCon10uumWindowLayoutPostion++;
                    }
                    return GetCon10uumWindowAttachedData(fe1).LayoutPosition.CompareTo(GetCon10uumWindowAttachedData(fe2).LayoutPosition);
                });
            return list;
        }

        /// <summary>
        /// Given an interpolation, the starting and ending positions and a number from 0-1 that represents how
        /// far along the virtual animation is, calculate the new position.
        /// </summary>
        Double EasingDouble(EasingFunctionBase easingFunction, double from, double to, double progress)
        {
            double alpha = easingFunction.Ease(progress);
            return from + alpha * (to - from);
        }

        private void ApplyInertia(double currentValue, double currentVelocity, out double newValue, out double newVelocity)
        {
            if (Double.IsNaN(currentValue) || Double.IsNaN(currentVelocity))
            {
                newValue = currentValue;
                newVelocity = 0;
                return;
            }

            #region Apply Resistance

            double resistanceForce = 0;
            if (currentVelocity == 0)
            {
                newValue = currentValue;
                newVelocity = 0;
                return;
            }

            if (currentVelocity > 0)
            {
                resistanceForce = -Resistance;
            }
            else
            {
                resistanceForce = Resistance;
            }

            newVelocity = currentVelocity + resistanceForce;

            if ((newVelocity > 0 && currentVelocity < 0) ||
                (newVelocity < 0 && currentVelocity > 0))
            {
                newValue = currentValue;
                newVelocity = 0;
                return;
            }

            #endregion

            //apply resistance force.
            //Why [ * 10]. It work but, I dont understand why. The DispatchTimer.Interval is set to 1 Milliseconds..... any idea ?
            newValue = currentValue + newVelocity * 10;

            //Check snap precision (aka stop items if newVelocity is too low.
            double snapPrecision = SnapPrecision / this.DesiredSize.Width;
            var abs = Math.Abs(newVelocity);
            if (abs < snapPrecision)
            {
                newVelocity = 0;
            }
        }

        #region IScrollInfo Members

        public bool CanHorizontallyScroll
        {
            get
            {
                return true;
            }
            set
            {
               
            }
        }

        public bool CanVerticallyScroll
        {
            get
            {
                return false;
            }
            set
            {
                
            }
        }

        public double HorizontalOffset
        {
            get { return m_Con10uumScrollData.ScrollInfoOffset.X; }
        }

        public double VerticalOffset
        {
            get { return m_Con10uumScrollData.ScrollInfoOffset.Y; }
        }

        public double ExtentHeight
        {
            get { return m_Con10uumScrollData.ScrollRegionExtentSize.Height; }
        }

        public double ExtentWidth
        {
            get { return m_Con10uumScrollData.ScrollRegionExtentSize.Width; }
        }

        public double ViewportHeight
        {
            get 
            { 
                return m_Con10uumScrollData.ScrollRegionViewportSize.Height; 
            }
        }

        public double ViewportWidth
        {
            get 
            {
                double finalSizeWidth = Math.Max(m_Con10uumScrollData.ScrollRegionViewportSize.Width, WindowMinWidth);
                finalSizeWidth = Math.Min(m_Con10uumScrollData.ScrollRegionViewportSize.Width, WindowMaxWidth);

                return finalSizeWidth; 
            }
        }

        public void LineDown()
        {
            
        }

        public void LineLeft()
        {
            SetHorizontalOffset(this.HorizontalOffset - 1);
        }

        public void LineRight()
        {
            SetHorizontalOffset(this.HorizontalOffset + 1);
        }

        public void LineUp()
        {
            
        }

        public Rect MakeVisible(Visual visual, Rect rectangle)
        {
            double d = 0.0;
            for (int i = 0; i < this.InternalChildren.Count; i++)
            {

                if ((Visual)this.InternalChildren[i] == visual)
                {
                    double visualRight = d + rectangle.Width;
                    if (visualRight > HorizontalOffset + ViewportWidth)
                    {
                        // now we can calculate the vertical offset that we need and set it
                        SetHorizontalOffset(HorizontalOffset - ((HorizontalOffset + ViewportWidth) - visualRight));
                    }
                    if (d < HorizontalOffset)
                    {
                        // now we can calculate the vertical offset that we need and set it
                        SetHorizontalOffset(d);
                    }
                    
                    // child size is always smaller than viewport, because that is what makes the Panel
                    // an AnnoyingPanel.
                    return rectangle;
                }
                d += ((UIElement)this.InternalChildren[i]).RenderSize.Width;
            }
            return rectangle;
            //throw new ArgumentException("Given visual is not in this Panel");
        }

        public void MouseWheelDown()
        {
            SetHorizontalOffset(this.HorizontalOffset + 10);
        }

        public void MouseWheelLeft()
        {
           
        }

        public void MouseWheelRight()
        {
            
        }

        public void MouseWheelUp()
        {
            SetHorizontalOffset(this.HorizontalOffset - 10);
        }

        public void PageDown()
        {
            double childWidth = (m_Con10uumScrollData.ScrollRegionViewportSize.Width * 2) / this.InternalChildren.Count;

            SetHorizontalOffset(this.HorizontalOffset + childWidth);
        }

        public void PageLeft()
        {
            
        }

        public void PageRight()
        {
            
        }

        public void PageUp()
        {
            double childWidth = (m_Con10uumScrollData.ScrollRegionViewportSize.Width * 2) / this.InternalChildren.Count;

            SetHorizontalOffset(this.HorizontalOffset - childWidth);
        }

        public ScrollViewer ScrollOwner{get;set;}

        public void SetVerticalOffset(double offset)
        {

        }

        public void SetHorizontalOffsetNoAnimation(double offset)
        {
            offset = CalculateMaxHorizontalOffsetPlusMargin(offset);

            m_Con10uumScrollData.ScrollInfoOffset.X = offset;

            if (ScrollOwner != null)
            {
                ScrollOwner.InvalidateScrollInfo();
            }

            ApplyScrollTranslateTransform(offset);
        }

        public void SetHorizontalOffset(double offset)
        {
            offset = CalculateMaxHorizontalOffsetPlusMargin(offset);
            
            m_Con10uumScrollData.ScrollInfoOffset.X = offset;

            TrackScrollHorizontalOffsetVelocity();

            if (ScrollOwner != null)
            {
                ScrollOwner.InvalidateScrollInfo();
            }

            //do not apply ScrollTranslateTransform. Let the ArrangeOverride do this with animation
        }

        private void ApplyScrollTranslateTransform(double horizontalOffset)
        {
            double scrollRatio = horizontalOffset / ExtentWidth;
            m_Con10uumScrollData.ScrollTranslateTransform.X = -(scrollRatio * (ExtentWidth * ZoomScaleTransform.ScaleX));
        }

        public double CalculateMaxHorizontalOffsetExact(double offset)
        {
            double extent = m_Con10uumScrollData.ScrollRegionExtentSize.Width;

            if (offset < 0 || m_Con10uumScrollData.ScrollRegionViewportSize.Width >= extent)
            {
                offset = 0;
            }
            else
            {
                if (offset + m_Con10uumScrollData.ScrollRegionViewportSize.Width >= extent)
                {
                    offset = extent - m_Con10uumScrollData.ScrollRegionViewportSize.Width;
                }
            }
            return offset;
        }

        public double CalculateMaxHorizontalOffsetPlusMargin(double offset)
        {
            double extent = m_Con10uumScrollData.ScrollRegionExtentSize.Width;

            if( m_Con10uumScrollData.ScrollRegionViewportSize.Width >= extent )
            {
                offset = 0;
            }
            else if (offset < -m_ElasticMargin)
            {
                offset = -m_ElasticMargin;
            }
            else
            {
                if (offset + m_Con10uumScrollData.ScrollRegionViewportSize.Width >= extent + m_ElasticMargin)
                {
                    offset = extent + m_ElasticMargin - m_Con10uumScrollData.ScrollRegionViewportSize.Width;
                }
            }
            return offset;
        }
      
        public void TrackScrollHorizontalOffsetVelocity()
        {
            DateTime now = DateTime.Now;
            if (m_Con10uumScrollData.LastRecordedHorizontalOffsetDate == DateTime.MinValue)
            {
                //Update Con10uumScrollVelocityData
                m_Con10uumScrollData.ResetAverageHorizontalOffsetVelocity();
                m_Con10uumScrollData.LastRecordedHorizontalOffsetDate = now;
                m_Con10uumScrollData.LastRecordedHorizontalOffset = HorizontalOffset;//in calculation
            }
            else
            {
                //v1  = (d1 - d0) / (t1 - t0)
                //v1 =  (d1 - v0) / (t1 - 1))                
                TimeSpan t1 = m_Con10uumScrollData.LastRecordedHorizontalOffsetDate - now;                              
                double d1 = m_Con10uumScrollData.LastRecordedHorizontalOffset - HorizontalOffset;
                double v0 = m_Con10uumScrollData.GetAverageHorizontalOffsetVelocity();
                double v1 = (d1 - v0) / (t1.Milliseconds - m_DispatcherTimer.Interval.Milliseconds);

                m_Con10uumScrollData.PushNewHorizontalOffsetVelocity(v1);                
                m_Con10uumScrollData.LastRecordedHorizontalOffset = HorizontalOffset;
                m_Con10uumScrollData.LastRecordedHorizontalOffsetDate = now;
            }
            
        }

        public class Con10uumScrollData
        {
            public Con10uumScrollData()
            {
                ScrollRegionExtentSize = new Size(0, 0);
                ScrollRegionViewportSize = new Size(0, 0);
            }

            internal Point ScrollInfoOffset;
            internal double LastRecordedHorizontalOffset { get; set; }
            internal double ElasticScrollRealHorizontalOffset { get; set; }
            internal DateTime LastRecordedHorizontalOffsetDate { get; set; }
            internal Size ScrollRegionExtentSize { get; set; }
            internal Size ScrollRegionViewportSize { get; set; }
            internal TranslateTransform ScrollTranslateTransform { get; set; }

            internal double ScrollToRightEndStartHorizontalOffset { get; set; }
            internal DateTime ScrollToRightEndStartDate { get; set; }
            internal DateTime ScrollToBoundaryStartDate { get; set; }

            internal bool HasNewElement { get; set; }

            internal int Con10uumWindowCount { get; set; }

            internal bool HasClosedElement { get; set; }

            internal DateTime ScrollLeftStartDate { get; set; }

            internal double ScrollLeftStartHorizontalOffset { get; set; }

            #region Scroll Average Velocity Calculation
            
            private const int MaxVelocities = 60;

            private Stack<Pair<double, DateTime>> m_VelocityStack = new Stack<Pair<double, DateTime>>(MaxVelocities);
            private double cachedVelocity = 0.0;
            private bool isCached = false;
            private DateTime lastAddedDate;

            public void ResetAverageHorizontalOffsetVelocity()
            {
                ResetAverageHorizontalOffsetVelocity(0);
            }

            public void ResetAverageHorizontalOffsetVelocity(double newHorizontalOffsetVelocity)
            {
                m_VelocityStack.Clear();
                isCached = false;                
                PushNewHorizontalOffsetVelocity(newHorizontalOffsetVelocity);
            }

            internal double GetAverageHorizontalOffsetVelocity()
            {
                if (isCached)
                {
                    return cachedVelocity;
                }

                DateTime now = DateTime.Now;
                Pair<double, DateTime>[] array = m_VelocityStack.ToArray();
                double velocitySum = 0.0;
                int velocitySumCount = 0;
                for (int i = 0; i < array.Length; i++)
                {
                    if (now - array[i].Value2 < TimeSpan.FromMilliseconds(100))
                    {
                        velocitySum += array[i].Value1;
                        velocitySumCount++;
                    }
                }
                
                cachedVelocity = (velocitySumCount != 0) ? velocitySum / velocitySumCount : 0;
                isCached = true;
                return cachedVelocity;
            }

            public void PushNewHorizontalOffsetVelocity(double horizontalOffsetVelocity)
            {
                if (m_VelocityStack.Count >= MaxVelocities)
                {
                    m_VelocityStack.Pop();
                }

                DateTime now = DateTime.Now;
                m_VelocityStack.Push(new Pair<double,DateTime>() { Value1 = horizontalOffsetVelocity , Value2 = now });
                isCached = false;
                lastAddedDate = now;
            }

            #endregion
        }

        #endregion

        /// <summary>
        /// This is an attached "property bag". Rather than have an attached property for data item we want to attach,
        /// it is more efficient to make the data items members of a class, and merely attach an instance of the class.
        /// </summary>
        private class Con10uumWindowAttachedData
        {
            internal Con10uumWindowAttachedData()
            {
                IsAnimating = true;
                StartLeftPosition = Unset;
                CurrentLeftPosition = Unset;
                TargetLeftPosition = Unset;
                LayoutPosition = -1;
            }

            internal bool IsAnimating { get; set; }
            internal DateTime StartTime { get; set; }

            internal double StartLeftPosition { get; set; }
            internal double CurrentLeftPosition { get; set; }
            internal double TargetLeftPosition { get; set; }

            /// <summary>
            /// Get/Set the layout position of the attached Con10uumWindow regarding all Con10uumWindows inside Con10uumPanel. 
            /// Smaller position = first Con10uumWindow on the left
            /// Highter position = last Con10uumWindow on the right
            /// </summary>
            internal int LayoutPosition { get; set; }

            internal TimeSpan GetElapsed(DateTime currentTime) { return currentTime - StartTime; }

            internal static readonly double Unset = -1;
        }
    }
}